package movee.api.aspect;

import movee.api.utils.ApiUtils;
import movee.domain.api.ApiConstants;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import io.github.resilience4j.ratelimiter.RateLimiter;
import io.github.resilience4j.ratelimiter.RateLimiterRegistry;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.lang.NonNull;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.UUID;

/**
 *
 *
 * @author movee
 */
@Slf4j
public class BasicApiAspect {

    private static final int MAX_PARAM_LEN = 100;

    @Getter
    private RateLimiter rateLimiter = null;
    private final Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create();

    /**
     * http AOP处理逻辑
     * @param jp 连接点
     * @return 返回结果
     * @throws Throwable 异常
     */
    public Object apiAroundAdvise(final ProceedingJoinPoint jp) throws Throwable {
        Instant start = Instant.now();
        Pair<String, String> httpContext = beforeProceedForHttp(jp, start);
        Object result = doProceed(jp);
        afterProceedForHttp(jp, httpContext.getLeft(), httpContext.getRight(), start);
        return result;
    }

    /**
     * AOP 前置处理
     * @param jp 连接点
     * @param start 入口时间
     * @return requestId和clientIp
     */
    private Pair<String, String> beforeProceedForHttp(ProceedingJoinPoint jp, Instant start) {

        String requestId = "";
        String clientRealIp = "";

        // http请求参数
        RequestAttributes reqAttrs = RequestContextHolder.getRequestAttributes();
        // http context
        if (reqAttrs != null) {
            // 获取controller方法签名，方法名，入参，入参值
            MethodSignature methodSignature = (MethodSignature) jp.getSignature();
            String targetName = methodSignature.getName();
            String[] pNames = methodSignature.getParameterNames();
            Object[] pValues = jp.getArgs();

            // 格式化请求参数
            String params = formatMethodParameters(pNames, pValues);

            // 获取请求ips
            HttpServletRequest httpRequest = ((ServletRequestAttributes) reqAttrs).getRequest();
            clientRealIp = ApiUtils.getClientRealIp(httpRequest);

            if (httpRequest.getAttribute(ApiConstants.X_API_REQUEST_ID) == null) {
                // 本次请求第一次调用
                requestId = UUID.randomUUID().toString();
                httpRequest.setAttribute(ApiConstants.X_API_REQUEST_ID, requestId);

                httpRequest.setAttribute(ApiConstants.API_REQUEST_START, start);
                httpRequest.setAttribute(ApiConstants.API_REQUEST_ENTRY, targetName);

                MDC.put(ApiConstants.X_API_REQUEST_ID, requestId);

                Logger logger = LoggerFactory.getLogger(jp.getTarget().getClass());
                logger.info("clientRealIp: {}, start {}, parameters: {}", clientRealIp, targetName, params);
            }
        }

        return Pair.of(requestId, clientRealIp);
    }

    /**
     * 调用实际方法
     * @param jp 连接点
     * @return 响应
     * @throws Throwable 异常
     */
    private Object doProceed(ProceedingJoinPoint jp) throws Throwable {
        Object result;
        if (rateLimiter == null) {
            result = jp.proceed();
        } else {
            // 开启限流
            result = RateLimiter.decorateCheckedSupplier(rateLimiter, jp::proceed).get();
        }
        return result;
    }

    private void afterProceedForHttp(ProceedingJoinPoint jp, String requestId, String clientRealIp, Instant start) {

        RequestAttributes reqAttrs = RequestContextHolder.getRequestAttributes();
        // http context
        if (reqAttrs != null) {
            HttpServletResponse httpResponse = ((ServletRequestAttributes) reqAttrs).getResponse();
            if (httpResponse != null) {
                // 设置返回http header信息
                httpResponse.setHeader(ApiConstants.X_API_REQUEST_ID, requestId);
                String date = LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME);
                httpResponse.setHeader(ApiConstants.X_API_DATE, date);
                httpResponse.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);

                // 打印http调用统计信息
                Duration duration = Duration.between(start, Instant.now());
                MethodSignature methodSignature = (MethodSignature) jp.getSignature();
                Logger logger = LoggerFactory.getLogger(jp.getTarget().getClass());
                logger.info("clientRealIp: {}, finish {}, duration time: {} ms",
                        clientRealIp, methodSignature.getName(), duration.toMillis());
            }
        }
    }

    /**
     * 将方法参数转换为字符串
     * @param pNames 参数名列表
     * @param pValues 参数值列表
     * @return 参数字符串
     */
    private String formatMethodParameters(@NonNull final String[] pNames, @NonNull final Object[] pValues) {

        int len = Math.min(pNames.length, pValues.length);
        StringBuilder sb = new StringBuilder();
        int i = len;
        while (i > 1) {
            int pos = len - i;
            // 如果存在name为cookie的参数，不打印
            if (!"cookie".equalsIgnoreCase(pNames[pos])) {
                String strValue = gson.toJson(pValues[pos]);
                if (strValue.length() > MAX_PARAM_LEN) {
                    strValue = strValue.substring(0, MAX_PARAM_LEN) + "......";
                }
                sb.append(String.format("%s=%s,", pNames[pos], strValue));
            }
            i--;
        }

        if (i == 1) {
            int pos = len - i;
            // 如果存在name为cookie的参数，不打印
            if (!"cookie".equalsIgnoreCase(pNames[pos])) {
                String strValue = gson.toJson(pValues[pos]);
                if (strValue.length() > MAX_PARAM_LEN) {
                    strValue = strValue.substring(0, MAX_PARAM_LEN) + "......";
                }
                sb.append(String.format("%s=%s", pNames[pos], strValue));
            }
        } else {
            sb.append("none");
        }

        return sb.toString();

    }

    public void setRateLimiter(RateLimiterRegistry registry) {
        if (registry != null) {
            rateLimiter = registry.find("apiTotalLimit").orElse(null);
        }
    }

}
